'use strict';

const isString = (val) => typeof val === 'string';
const isObject = (val) => {
    return val !== null && typeof val === 'object';
};
const isFunction = (val) => typeof val === 'function';
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
// add->Add
const capitialize = (str) => {
    return str.charAt(0).toUpperCase() + str.slice(1);
};
// add->onAdd
const toHandlerKey = (str) => {
    return str ? 'on' + capitialize(str) : '';
};
// add-foo ->  addFoo
const camelize = (str) => {
    return str.replace(/-(\w)/g, (_, c) => {
        return c ? c.toUpperCase() : '';
    });
};

const publicPropertiesMap = {
    $el: (i) => i.vnode.el,
    $slots: (i) => i.slots
};
const publicInstanceProxyHandlers = {
    get({ _: instance }, key) {
        const { setupState, props } = instance;
        if (hasOwn(setupState, key)) {
            return setupState[key];
        }
        else if (hasOwn(props, key)) {
            return props[key];
        }
        const publicGetter = publicPropertiesMap[key];
        if (publicGetter) {
            return publicGetter(instance);
        }
    }
};

const initProps = (instance, rawProps) => {
    instance.props = rawProps || {};
};

const targetMap = new WeakMap();
function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (depsMap) {
        const depSet = depsMap.get(key);
        if (depSet) {
            triggerEffect(depSet);
        }
    }
}
function triggerEffect(dep) {
    for (const effect of dep) {
        if (effect.scheduler) {
            effect.scheduler();
        }
        else {
            effect.run();
        }
    }
}

const get = createGetter();
const set = createSetter();
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true, true);
function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key) {
        const res = Reflect.get(target, key);
        if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
            return !isReadonly;
        }
        if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
            return isReadonly;
        }
        if (shallow) {
            return res;
        }
        if (isObject(res)) {
            return isReadonly ? readonly(res) : reactive(res);
        }
        return res;
    };
}
function createSetter() {
    return function set(target, key, value) {
        const res = Reflect.set(target, key, value);
        trigger(target, key);
        return res;
    };
}
const mutableHandles = {
    get,
    set
};
const readonlyHandles = {
    get: readonlyGet,
    set(target, key, value) {
        console.warn(`${isString(key) ? key : String(key)}是只读的属性，不能被修改`);
        return true;
    }
};
const shallowReadonlyHandles = Object.assign({}, readonlyHandles, {
    get: shallowReadonlyGet
});

function reactive(raw) {
    return createActiveObject(raw, mutableHandles);
}
function readonly(raw) {
    return createActiveObject(raw, readonlyHandles);
}
function shallowReadonly(raw) {
    return createActiveObject(raw, shallowReadonlyHandles);
}
function createActiveObject(raw, baseHandles) {
    return new Proxy(raw, baseHandles);
}

const emit = (instance, event, ...args) => {
    console.log('event', event);
    const { props } = instance;
    const handlerName = toHandlerKey(event);
    const handler = props[camelize(handlerName)];
    handler && handler(...args);
};

function initSlots(instance, children) {
    if (instance.vnode.shapeFlag & 16 /* ShapeFlags.SLOTS_CHILDREN */) {
        normalizeObjectSlots(children, instance.slots);
    }
    function normalizeObjectSlots(children, slots) {
        for (const key in children) {
            const value = children[key];
            slots[key] = (props) => normalizeSlotValue(value(props));
        }
    }
}
function normalizeSlotValue(value) {
    return Array.isArray(value) ? value : [value];
}

function createComponentInstance(vnode) {
    const component = {
        vnode: vnode,
        type: vnode.type,
        setupState: {},
        proxy: null,
        props: {},
        slots: {},
        emit: () => { }
    };
    component.emit = emit.bind(null, component);
    return component;
}
function setupComponent(instance) {
    // setup props
    initProps(instance, instance.vnode.props);
    initSlots(instance, instance.vnode.children);
    // setup slots
    setupStatefulComponent(instance);
}
function setupStatefulComponent(instance) {
    const component = instance.type;
    instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandlers);
    const { setup } = component;
    const { props, emit } = instance;
    if (setup) {
        const setupResult = setup(shallowReadonly(props), { emit });
        handleSetupResult(instance, setupResult);
    }
}
function handleSetupResult(instance, setupResult) {
    if (isObject(setupResult)) {
        instance.setupState = setupResult;
    }
    finishComponentSetup(instance);
}
function finishComponentSetup(instance) {
    const component = instance.type;
    if (component.render) {
        instance.render = component.render;
    }
}

function render(vnode, container) {
    patch(vnode, container);
}
function patch(vnode, container) {
    const { type, shapeFlag } = vnode;
    if (shapeFlag & 1 /* ShapeFlags.ELEMENT */) {
        processElement(vnode, container);
    }
    else if (shapeFlag & 2 /* ShapeFlags.STATEFULCOMPONENT */) {
        processComponent(vnode, container);
    }
}
function processComponent(vnode, container) {
    mountComponent(vnode, container);
}
function mountComponent(initialVNode, container) {
    const instance = createComponentInstance(initialVNode);
    setupComponent(instance);
    setupRenderEffect(instance, initialVNode, container);
}
function setupRenderEffect(instance, initialVNode, container) {
    const { proxy } = instance;
    const subTree = instance.render.call(proxy);
    patch(subTree, container);
    initialVNode.el = subTree.el;
}
function processElement(vnode, container) {
    mountElement(vnode, container);
}
function mountElement(vnode, container) {
    const node = (vnode.el = document.createElement(vnode.type));
    const { props, children, shapeFlag } = vnode;
    if (props) {
        for (const key in props) {
            const value = props[key];
            const isOn = /^on[A-Z]/.test(key);
            if (isOn) {
                node.addEventListener(key.slice(2).toLowerCase(), value);
            }
            else {
                node.setAttribute(key, value);
            }
        }
    }
    if (children) {
        // if (typeof children === 'string') {
        if (shapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) {
            node.textContent = vnode.children;
        }
        else if (shapeFlag & 8 /* ShapeFlags.ARRAY_CHILDREN */) {
            mountChildren(vnode, node);
        }
    }
    container.append(node);
}
function mountChildren(vnode, container) {
    vnode.children.forEach((v) => {
        patch(v, container);
    });
}

function createVNode(type, props, children) {
    const node = {
        type,
        props,
        children,
        el: null,
        shapeFlag: getShapeFlag(type)
    };
    if (typeof children === 'string') {
        node.shapeFlag = node.shapeFlag | 4 /* ShapeFlags.TEXT_CHILDREN */;
    }
    else if (Array.isArray(children)) {
        node.shapeFlag = node.shapeFlag | 8 /* ShapeFlags.ARRAY_CHILDREN */;
    }
    // slots 组件+children,object
    if (node.shapeFlag & 2 /* ShapeFlags.STATEFULCOMPONENT */) {
        if (typeof children === 'object') {
            node.shapeFlag = node.shapeFlag | 16 /* ShapeFlags.SLOTS_CHILDREN */;
        }
    }
    return node;
}
function getShapeFlag(type) {
    if (typeof type === 'string') {
        return 1 /* ShapeFlags.ELEMENT */;
    }
    else {
        return 2 /* ShapeFlags.STATEFULCOMPONENT */;
    }
}

function createApp(rootComponent) {
    return {
        mounted(container) {
            const vnode = createVNode(rootComponent);
            render(vnode, container);
        }
    };
}

function h(type, props, children) {
    return createVNode(type, props, children);
}

function renderSlots(slots, name, props) {
    const slot = slots[name];
    if (isFunction(slot)) {
        return createVNode('div', {}, slot(props));
    }
}

exports.createApp = createApp;
exports.h = h;
exports.renderSlots = renderSlots;
